﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Algorithms.Expressions
{
    public class ExpressionTreeReducer : ExpressionVisitor
    {
        protected override Expression VisitBinary(BinaryExpression node)
        {
            Expression result = null;

            // by calling visist, we make sure that internal complexity is also reduced 
            Expression left = Visit(node.Left);
            Expression right = Visit(node.Right);

            // if the arguments are 2 constants, dont evaluate, just calculate here and return that as a constant
            if (left.NodeType == ExpressionType.Constant && right.NodeType == ExpressionType.Constant)
            {
                ConstantExpression constantLeft = (ConstantExpression)left;
                ConstantExpression constantRight = (ConstantExpression)right;

                double leftValue = (double)constantLeft.Value;
                double rightValue = (double)constantRight.Value;

                switch (node.NodeType)
                {
                    case ExpressionType.Add:
                        result = Expression.Constant(leftValue + rightValue);
                        break;
                    case ExpressionType.Subtract:
                        result = Expression.Constant(leftValue - rightValue);
                        break;
                    case ExpressionType.Multiply:
                        result = Expression.Constant(leftValue * rightValue);
                        break;
                    case ExpressionType.Divide:
                        result = Expression.Constant(leftValue / rightValue);
                        break;
                    case ExpressionType.Power:
                        result = Expression.Constant(leftValue * rightValue);
                        break;
                }
            }
            else
            {
                ConstantExpression constantExpression;
                Expression otherExpression;

                // Either left or right can be constant
                if (node.Left.NodeType == ExpressionType.Constant)
                {
                    constantExpression = (ConstantExpression)node.Left;
                    otherExpression = node.Right;
                }
                else if (node.Right.NodeType == ExpressionType.Constant)
                {
                    constantExpression = (ConstantExpression)node.Right;
                    otherExpression = node.Left;
                }
                else
                {
                    // None are
                    constantExpression = null;
                    otherExpression = null;
                }

                if (constantExpression != null)
                {
                    double constantValue = (double)constantExpression.Value;

                    switch (node.NodeType)
                    {
                        case ExpressionType.Add:
                        case ExpressionType.Subtract:
                            // in case of an addition or substraction of 0, we dont need to do anything
                            if (constantValue == 0)
                            {
                                result = otherExpression;
                            }
                            break;
                        case ExpressionType.Multiply:
                        case ExpressionType.Divide:
                        case ExpressionType.Power:
                            // in case of these, when constant value is 1 return the other part (no valid operation)
                            if (constantValue == 1)
                            {
                                return otherExpression;
                            }
                            break;
                    }
                }
            }

            // on default
            if (result == null)
            {
                result = base.VisitBinary(node);
            }

            return result;
        }
    }
}
